package ar.edu.itba.pod.legajo46470.communication;

import static com.google.common.base.Preconditions.checkNotNull;

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;

import org.apache.log4j.Logger;

import ar.edu.itba.node.NodeInformation;
import ar.edu.itba.pod.legajo46470.cluster.node.ClusterNode;

public class ClusterCommunication<T> {
	private final static Logger LOGGER = Logger.getLogger(ClusterCommunication.class);
	private final static Integer MAX_GOSSIP_NODES = 10; 
	private ClusterNode clusterNode;
	private String lookupBind;
	private final List<NodeInformation> neighbours;
	private final Random neighbourChooser;
	
	
	public ClusterCommunication(ClusterNode clusterNode, String lookupBind) {
		checkNotNull(clusterNode, "Cluster Node cannot be null");
		checkNotNull(lookupBind, "lookup name cannot be null");
		this.clusterNode = clusterNode;
		this.lookupBind = lookupBind;
		neighbours = new ArrayList<NodeInformation>();
		neighbourChooser = new Random();
	}
	
	public ClusterNode getClusterNode() {
		return clusterNode;
	}
	
	@SuppressWarnings("unchecked")
	public T getStub(NodeInformation node) throws IllegalStateException {
		synchronized (ClusterCommunication.class) {
			
		
			T stub = null;
			try {
				try {
					final Registry registry = LocateRegistry.getRegistry(node.host(), node.port());
					stub = (T) registry.lookup(lookupBind);
					return stub;
				} catch (RemoteException e) {
					clusterNode.getClusterAdministration().disconnectFromGroup(node);
					throw new IllegalStateException();
				} catch (NotBoundException e) {
					clusterNode.getClusterAdministration().disconnectFromGroup(node);
					throw new IllegalStateException();
				}
			} catch(RemoteException e) {
				// never happens
			} catch (NotBoundException e) {
				// never happens
			}
			
			return stub;
		}
	}
	
	public Set<NodeInformation> getRandomNeighbours() {
		Set<NodeInformation> selectedNeighbours = new HashSet<NodeInformation>();
		// TODO este sincronized es MUY grande ver de sacar o mejorar
		synchronized (ClusterCommunication.class) {
			NodeInformation selectedNode;
			selectedNeighbours.clear();
			int i = 0;
			
			// Gets all neighbours
			try {
				neighbours.addAll(clusterNode.getClusterAdministration().connectedNodes());
				
			} catch (RemoteException e) {
				LOGGER.error("Error in local stub, remote exception");
			}
			
			// selects at most MAX_GOSSIP_NODES neighbours to broadcast message
			while (i < Math.min(MAX_GOSSIP_NODES , neighbours.size() - 1) && neighbours.size() > 1) {
				selectedNode = neighbours.get(neighbourChooser.nextInt(neighbours.size()));
				if(!selectedNeighbours.contains(selectedNode) && !(clusterNode.getNodeInfo().equals(selectedNode))) {
					selectedNeighbours.add(selectedNode);
					i++;
				}
			}
			
			neighbours.clear();
			return selectedNeighbours;
		}
	}
	
	public void disconnectNode(NodeInformation n) {
		try {
			clusterNode.getClusterAdministration().disconnectFromGroup(n);
		} catch (RemoteException e) {
			// never happens
		} catch (NotBoundException e) {
			// never happens
		}
	}
}
